import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文本文件字符数量统计类
 *
 * @author billy
 */
public class CharacterCount {
    /** 读取文件的后缀类型 */
    private static final String COUNT_FILE_SUFFIX = ".txt";
    /** 保存统计结果的文件名 */
    private static final String COUNT_RESULT_FILE_NAME = "CountResult.txt";
    /** 正则表达式-过滤中文规则 */
    private static final Pattern PATTERN_CHARACTER_CN = Pattern.compile("[\\u4e00-\\u9fa5]");
    /** 正则表达式-过滤英文规则 */
    private static final Pattern PATTERN_CHARACTER_EN = Pattern.compile("[a-zA-Z]");
    /** 正则表达式-过滤数字规则 */
    private static final Pattern PATTERN_CHARACTER_NUMBER = Pattern.compile("\\d");
    /** 正则表达式-过滤标点符号规则 */
    private static final Pattern PATTERN_CHARACTER_SYMBOL = Pattern.compile("[,，.。、<>《》/?？;；'‘’\"“”:：\\[\\]{}【】\\\\|~!@#$%^&*()_\\-+=！￥…（）—]");
    /** 正则表达式-过滤空格字符规则 */
    private static final Pattern PATTERN_CHARACTER_SPACE = Pattern.compile("\\s");
    //---------------------------
    /** 统计结果存储的map中的key——中文字数 */
    private static final String KEY_CHARACTER_COUNT_CN = "cnCharacterCount";
    /** 统计结果存储的map中的key——英文字数 */
    private static final String KEY_CHARACTER_COUNT_EN = "enCharacterCount";
    /** 统计结果存储的map中的key——数字数 */
    private static final String KEY_CHARACTER_COUNT_NUMBER = "numberCharacterCount";
    /** 统计结果存储的map中的key——标点+符号数 */
    private static final String KEY_CHARACTER_COUNT_SYMBOL = "symbolCharacterCount";
    /** 统计结果存储的map中的key——空格数 */
    private static final String KEY_CHARACTER_COUNT_SPACE = "spaceCharacterCount";
    /** 统计结果存储的map中的key——行数 */
    private static final String KEY_COUNT_LINE = "lineCount";
    /** 统计结果存储的map中的key——总字符数合计 */
    private static final String KEY_CHARACTER_COUNT_TOTAL = "totalCharacterCount";
    /** 字节转为MB所需要整除的倍数 */
    private static final int BYTE_TO_MB_SIZE = 1024 * 1024;
    /** 获取cpu物理核心数 */
    private static final int CPU_CORE_COUNT = Runtime.getRuntime().availableProcessors();


    public static void main(String[] args) {
        startHandle();
    }

    private static void startHandle() {
        handleInput();
    }

    /**
     * 读取用户指定路径的文件
     * G:\TDDOWNLOAD\Reader\a.txt
     * G:\TDDOWNLOAD\Reader\
     */
    private static void handleInput() {
        final Scanner in = new Scanner(System.in);
        while (true) {
            System.out.println("请输入文本文件的完整路径或所在的文件夹（退出程序请输入exit）：");
            String filePath = in.next();
            if ("exit".equals(filePath.toLowerCase())) {
                break;
            }
            //检查文件是否存在
            final File file = new File(filePath);
            if (!file.exists()) {
                System.out.println("[ERROR]路径不存在！");
                continue;
            }
            if (file.isFile()) {
                //读取单个文件时
                countSingleFile(file);
            } else if (file.isDirectory()) {
                //读取一个文件夹时
                countDirectoryFile(file);
            }
        }
        close(in);
    }

    /**
     * 读取单个文件
     * G:\TDDOWNLOAD\Reader\a.txt
     */
    private static void countSingleFile(final File file) {
        final Scanner in = new Scanner(System.in);
        //字符编码
        System.out.println("请输入文本文件的字符编码（输入任意单个字符，则程序智能识别）：");
        String charset = in.next();
        if (charset.length() == 1) {
            charset = getFileCharset(file);
        }
        final long startTime = System.currentTimeMillis();
        //获取读取文件后的字符信息map
        Map<String, Integer> fwiMap = getFileCharacterInfoMap(file, charset, true);
        StringBuilder sb = new StringBuilder(128);
        //存储并格式化结果
        sb.append("中文字数：").append(fwiMap.get(KEY_CHARACTER_COUNT_CN)).append("\r\n");
        sb.append("英文字数：").append(fwiMap.get(KEY_CHARACTER_COUNT_EN)).append("\r\n");
        sb.append("数字数：").append(fwiMap.get(KEY_CHARACTER_COUNT_NUMBER)).append("\r\n");
        sb.append("标点+符号数：").append(fwiMap.get(KEY_CHARACTER_COUNT_SYMBOL)).append("\r\n");
        sb.append("空格数：").append(fwiMap.get(KEY_CHARACTER_COUNT_SPACE)).append("\r\n");
        sb.append("行数：").append(fwiMap.get(KEY_COUNT_LINE)).append("\r\n");
        sb.append("----->【合计】").append("\r\n");
        sb.append("文件大小：").append(getFileLengthByByteToMb(file)).append("\r\n");
        sb.append("所有字符总数：").append(fwiMap.get(KEY_CHARACTER_COUNT_TOTAL));
        //输出结果
        System.out.println(sb.toString());
        System.out.println(getTimeSecondDiff(startTime, System.currentTimeMillis()));
        System.out.println("--------------------分隔符--------------------");
    }

    /**
     * 读取一个文件夹中的所有文件
     * G:\TDDOWNLOAD\Reader\test
     */
    private static void countDirectoryFile(final File file) {
        //删除旧的读取结果文件
        final File countResultFile = new File(file, COUNT_RESULT_FILE_NAME);
        if (countResultFile.exists()) {
            countResultFile.delete();
        }
        //读取该文件夹下的所有文件（使用lambda表达式来设置文件过滤器，只需要txt文件）
        final File[] files = file.listFiles(
                file1 -> file1.isFile() && file1.getName().toLowerCase().endsWith(COUNT_FILE_SUFFIX)
        );
        if (files == null) {
            return;
        }
        //数组进行排序
        sortFileArray(files);
        final Scanner in = new Scanner(System.in);
        //字符编码
        System.out.println("请选择读取线程模式（0：单线程，否则多线程）：");
        int model = parseInt(in.next(), 1);
        if (model == 0) {
            //使用单线程来运行
            countDirectoryFileBySingleThread(files, countResultFile);
        } else {
            //使用线程池多线程来运行
            countDirectoryFileByThreadPool(files, countResultFile);
        }
    }

    /**
     * 读取一个文件夹的所有文件字符信息（单一线程）
     */
    private static void countDirectoryFileBySingleThread(File[] files, File countResultFile) {
        final long startTime = System.currentTimeMillis();
        final int len = files.length;
        Map<String, Integer> fwiMap;
        File file1;
        String charset;
        final StringBuilder sbResult = new StringBuilder(256);
        final StringBuilder sbFile = new StringBuilder(64);
        System.out.println("----->[单线程]开始统计文件字符数，请稍候...");
        for (int i = 0; i < len; i++) {
            file1 = files[i];
            //字符编码
            charset = getFileCharset(file1);
            //获取读取文件后的字符信息map
            fwiMap = getFileCharacterInfoMap(file1, charset, false);
            sbFile.append(file1.getName()).append(", ")
                    .append(getFileLengthByByteToMb(file1)).append(", ")
                    .append(String.format("%.2f", fwiMap.get(KEY_CHARACTER_COUNT_TOTAL) / 10000.0)).append("万");
            System.out.println(sbFile.toString());
            sbResult.append(sbFile).append("\r\n");
            //清空
            sbFile.setLength(0);
        }
        System.out.println("----->[单线程]文件字符数统计结束！");
        System.out.println(getTimeSecondDiff(startTime, System.currentTimeMillis()));
        System.out.println("--------------------分隔符--------------------");
        //将读取结果写入文件
        writeToFile(countResultFile, sbResult.toString());
    }

    /**
     * 读取一个文件夹的所有文件字符信息（线程池多线程）
     */
    private static void countDirectoryFileByThreadPool(File[] files, File countResultFile) {
        final long startTime = System.currentTimeMillis();
        final int taskSize = files.length;
        final List<Future<?>> futureList = new ArrayList<>();
        ExecutorService threadPool = null;
        try {
            threadPool = Executors.newFixedThreadPool(Math.min(taskSize, CPU_CORE_COUNT));
            System.out.println("----->[多线程]开始统计文件字符数，请稍候...");
            for (int i = 0; i < taskSize; i++) {
                final File file = files[i];
                Future<?> future = threadPool.submit(() -> {
                    //字符编码
                    String charset = getFileCharset(file);
                    //获取读取文件后的字符信息map
                    Map<String, Integer> fwiMap = getFileCharacterInfoMap(file, charset, false);
                    String result = file.getName() + ", " +
                            getFileLengthByByteToMb(file) + ", " +
                            String.format("%.2f", fwiMap.get(KEY_CHARACTER_COUNT_TOTAL) / 10000.0) + "万\r\n";
                    //写入文件
                    writeToFile(countResultFile, result);
                    System.out.print(Thread.currentThread().getName() + "->" + result);
                });
                futureList.add(future);
            }
            //检查每一个任务是否执行完成
            futureList.forEach(objectFuture -> {
                try {
                    //等待任务执行完成，建议使用get()方式
//                    while (!objectFuture.isDone()) {
//                    }
                    //获取线程执行返回后的结果，当线程为执行完成时，则一直处于阻塞状态
                    objectFuture.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            System.out.println("----->[多线程]文件字符数统计结束！");
            System.out.println(getTimeSecondDiff(startTime, System.currentTimeMillis()));
            System.out.println("--------------------分隔符--------------------");
            //关闭线程池
            threadPool.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (threadPool != null && !threadPool.isShutdown()) {
                threadPool.shutdown();
            }
        }
    }

    /**
     * 将数据写入指定的文件中
     */
    private static void writeToFile(final File file, final String data) {
        FileWriter fw = null;
        try {
            fw = new FileWriter(file, true);
            fw.write(data);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(fw);
        }
    }

    /**
     * 根据传入的file来计算该文件中的字符信息的集合map
     */
    private static Map<String, Integer> getFileCharacterInfoMap(final File file, final String charset,
                                                                final boolean isPrintConsoleLog) {
        Map<String, Integer> map = new HashMap<>(16);
        if (isPrintConsoleLog) {
            //添加个空白行
            System.out.println("字符编码：" + charset);
            System.out.println("----->开始统计文件字符数，请稍候...");
        }
        //中文字数
        int cnCharacterCount = 0;
        //英文字数
        int enCharacterCount = 0;
        //数字数
        int numberCharacterCount = 0;
        //标点符号数
        int symbolCharacterCount = 0;
        //空格数
        int spaceCharacterCount = 0;
        //行数
        int lineCount = 0;
        BufferedReader br = null;
        try {
            if (isPrintConsoleLog) {
                System.out.println("--------------------前10行开始--------------------");
            }
            br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
            String str;
            while ((str = br.readLine()) != null) {
                cnCharacterCount += countCnCharacter(str);
                enCharacterCount += countEnCharacter(str);
                numberCharacterCount += countNumberCharacter(str);
                symbolCharacterCount += countSymbolCharacter(str);
                spaceCharacterCount += countSpaceCharacter(str);
                lineCount++;
                //打印前10行
                if (isPrintConsoleLog && lineCount <= 10) {
                    System.out.println(str);
                }
            }
            if (isPrintConsoleLog) {
                System.out.println("--------------------前10行结束--------------------");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(br);
        }
        if (isPrintConsoleLog) {
            System.out.println("----->文件字符数统计结束！");
        }
        map.put(KEY_CHARACTER_COUNT_CN, cnCharacterCount);
        map.put(KEY_CHARACTER_COUNT_EN, enCharacterCount);
        map.put(KEY_CHARACTER_COUNT_NUMBER, numberCharacterCount);
        map.put(KEY_CHARACTER_COUNT_SYMBOL, symbolCharacterCount);
        map.put(KEY_CHARACTER_COUNT_SPACE, spaceCharacterCount);
        map.put(KEY_COUNT_LINE, lineCount);
        map.put(KEY_CHARACTER_COUNT_TOTAL,
                (cnCharacterCount + enCharacterCount + numberCharacterCount + symbolCharacterCount + spaceCharacterCount));
        return map;
    }

    /**
     * 对数组进行排序
     */
    private static void sortFileArray(final File[] files) {
        int len = files.length;
        File tempFile;
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                if (files[i].length() > files[j].length()) {
                    tempFile = files[i];
                    files[i] = files[j];
                    files[j] = tempFile;
                }
            }
        }
    }

    /**
     * 获取指定文件的大小，单位为：MB
     */
    private static String getFileLengthByByteToMb(File file) {
        return String.format("%.2fMB", 1.0 * file.length() / BYTE_TO_MB_SIZE);
    }

    /**
     * 将2个时间（毫秒）的时间差转为为秒
     */
    private static String getTimeSecondDiff(long start, long end) {
        return String.format("耗时 %.2f 秒", (end - start) / 1000.0);
    }

    /**
     * 统计中文字数
     */
    private static int countCnCharacter(final String str) {
        int count = 0;
        Matcher m = PATTERN_CHARACTER_CN.matcher(str);
        while (m.find()) {
            count++;
        }
        return count;
    }

    /**
     * 统计英文字数
     */
    private static int countEnCharacter(final String str) {
        int count = 0;
        Matcher m = PATTERN_CHARACTER_EN.matcher(str);
        while (m.find()) {
            count++;
        }
        return count;
    }

    /**
     * 统计数字数
     */
    private static int countNumberCharacter(final String str) {
        int count = 0;
        Matcher m = PATTERN_CHARACTER_NUMBER.matcher(str);
        while (m.find()) {
            count++;
        }
        return count;
    }

    /**
     * 统计标点符号
     */
    private static int countSymbolCharacter(final String str) {
        int count = 0;
        Matcher m = PATTERN_CHARACTER_SYMBOL.matcher(str);
        while (m.find()) {
            count++;
        }
        return count;
    }

    /**
     * 统计空格数
     */
    private static int countSpaceCharacter(final String str) {
        int count = 0;
        Matcher m = PATTERN_CHARACTER_SPACE.matcher(str);
        while (m.find()) {
            count++;
        }
        return count;
    }

    /**
     * 关闭释放各种资源
     */
    private static void close(final AutoCloseable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将String转为int类型，若转化失败，则返回默认值
     */
    private static int parseInt(String s, int defValue) {
        try {
            return Integer.parseInt(s);
        } catch (Exception e) {
            return defValue;
        }
    }

    /**
     * 判断文件的编码格式
     *
     * @param file :file
     * @return 文件编码格式
     */
    private static String getFileCharset(final File file) {
        BufferedInputStream bin = null;
        String code = "GBK";
        try {
            bin = new BufferedInputStream(new FileInputStream(file));
            int p = (bin.read() << 8) + bin.read();
            switch (p) {
                case 0xefbb:
                    code = "UTF-8";
                    break;
                case 0xfffe:
                    code = "Unicode";
                    break;
                case 0xfeff:
                    code = "UTF-16BE";
                    break;
                default:
                    code = "GBK";
            }
        } catch (IOException e) {
            System.out.println("[error]" + file.getName());
            e.printStackTrace();
        } finally {
            close(bin);
        }
        return code;
    }
}
